
#include "ReproductionOperators.h"


#include "TreeOfNodes.h"

#include "GpModel.h"

#include "Node.h"
#include "FunctionNode.h"
#include "TerminalNode.h"
#include "CrossoverOperators.h"
#include "MutationOperators.h"

#include <vector>

namespace jcrada {

    GpModel::GpModel( int population_size ) :
    _population_size( population_size ),
    _trees( NULL ),
    _tree_generator( new TreeGenerator( ) ),
    _lower_fitness_better( true ),
    _tournament_selection( 2 ),
    _probability_crossover( 0.9 ),
    _probability_mutation( 0.01 ),
    _probability_reproduction( 1 - (_probability_crossover + _probability_mutation) ) {
        _trees = new TreeOfNodes* [getPopulationSize()];
        for (int i = 0; i < getPopulationSize(); ++i) {
            setTree(NULL, i);
        }
    }

    GpModel::~GpModel( ) {
        delete _tree_generator;
        for (int i = 0; i < getPopulationSize(); ++i) {
            delete _trees[i];
        }
        delete [] _trees;
    }

    void GpModel::create( ) {
        for (int i = 0; i < getPopulationSize(); ++i) {
            setTree(getTreeGenerator().createRandomTree(), i);
        }
        g_operator[GO_CROSSOVER] = getProbabilityCrossover();
        if (getProbabilityReproduction() > getProbabilityMutation()) {
            g_operator[1] = getProbabilityReproduction();
            g_operator[2] = getProbabilityMutation();
        } else {
            g_operator[1] = getProbabilityMutation();
            g_operator[2] = getProbabilityReproduction();
        }

    }

    TreeOfNodes& GpModel::bestTournament( int k ) {
        if (k <= 0) {
            k = getTournamentSelection();
        }
        TreeOfNodes** tournament = new TreeOfNodes*[k];
        for (int i = 0; i < k; ++i) {
            tournament[i] = &getTree(_rnd_tournament.nextInt(0, getPopulationSize()));
        }
        TreeOfNodes* result = NULL;
        double fitness = isLowerFitnessBetter() ? 1.0 / 0.0 : -1.0 / 0.0;
        for (int i = 0; i < k; ++i) {
            if (isBetter(tournament[i]->getFitness(), fitness)) {
                fitness = tournament[i]->getFitness();
                result = tournament[i];
            }
        }
        for (int i = 0; i < k; ++i) {
            tournament[i] = NULL;
        }
        delete [] tournament;
        return *result;
    }

    TreeOfNodes& GpModel::worstTournament( int k ) {
        if (k <= 0) {
            k = getTournamentSelection();
        }
        TreeOfNodes** tournament = new TreeOfNodes*[k];
        for (int i = 0; i < k; ++i) {
            tournament[i] = &getTree(_rnd_tournament.nextInt(0, getPopulationSize()));
        }
        TreeOfNodes* result = NULL;
        double fitness = isLowerFitnessBetter() ? -1.0 / 0.0 : 1.0 / 0.0;
        for (int i = 0; i < k; ++i) {
            if (!isBetter(tournament[i]->getFitness(), fitness)) {
                fitness = tournament[i]->getFitness();
                result = tournament[i];
            }
        }
        for (int i = 0; i < k; ++i) {
            tournament[i] = NULL;
        }
        delete [] tournament;
        return *result;
    }

    GpModel::eGeneticOperator GpModel::nextGeneticOperator( ) {
        double probability = _rnd_genetic_operator.nextDouble();
        for (int i = 0; i < GO_COUNT; ++i) {
            if (probability < g_operator[i]) {
                return (eGeneticOperator) i;
            } else {
                probability -= g_operator[i];
            }
        }
        return GO_NONE;
    }

    void GpModel::step( ) {
        TreeOfNodes** next = new TreeOfNodes*[getPopulationSize()];
        eGeneticOperator go = GO_NONE;
        for (int i = 0; i < getPopulationSize(); ++i) {
            go = nextGeneticOperator();
            switch (go) {
                case GO_CROSSOVER:
                {
                    TreeOfNodes* offspring = bestTournament().clone();
                    getCrossoverOperator()->crossover(*offspring, bestTournament());
                    next[i] = offspring;
                }
                    break;
                case GO_REPRODUCTION:
                    next[i] = getReproductionOperator()->reproduce(bestTournament());
                    break;
                case GO_MUTATION:
                    next[i] = getMutationOperator()->mutate(bestTournament(1), getTreeGenerator());
                    break;
                default:
                    BDEBUG(TO_STRING(go));
                    assert(0);
            }
        }
        for (int i = 0; i < getPopulationSize(); ++i) {
            delete _trees[i];
        }
        delete _trees;
        _trees = next;
    }

    TerminalNode * GpModel::evaluate( const TreeOfNodes& tree, FunctionListener & listener ) {
        return evaluate(tree.getRoot(), listener);
    }

    TerminalNode * GpModel::evaluate( const Node* node, FunctionListener & listener ) {
        TerminalNode* result = NULL;
        const FunctionNode* function = dynamic_cast<const FunctionNode*> (node);
        if (function) {
            FunctionNode* tmp_function = new FunctionNode(function->getArity());
            tmp_function->setType(function->getType());
            for (int i = 0; i < function->getArity(); ++i) {
                evaluate(function->getChild(i), listener)->moveTo(tmp_function, i);
            }
            listener.evaluate(*tmp_function);
            for (int i = 0; i < tmp_function->getArity() - 1; ++i) {
                delete tmp_function->getChild(i);
            }

            result = dynamic_cast<TerminalNode*> (tmp_function->getChild(tmp_function->getArity() - 1));
            assert(result);
            result->remove();
            delete tmp_function;
        } else {
            const TerminalNode* terminal = dynamic_cast<const TerminalNode*> (node);
            if (terminal) {
                result = terminal->clone();
            } else {
                if (node == NULL) {
                    BDEBUG("Node is NULL");
                } else {
                    BDEBUG("Unknown type of Node");
                }
                assert(0);
            }
        }
        return result;
    }

    class TestFunctionListener : public FunctionListener {
    public:

        TestFunctionListener( ) : FunctionListener( ) {
        }

        virtual void evaluate( const FunctionNode& function ) {
            return;
            return;
            switch (function.getType()) {
                case FunctionNode::FT_MOVE :
                {
                    //suma
                    //                    int result = 0;
                    //                    for (int i = 0; i < arity - 1; ++i) {
                    //                        result += arguments[i]->getValue();
                    //                    }
                    //                    arguments[arity - 1]->setValue(result);
                    //                    std::cout << function.toString() << std::endl;
                    break;
                }
                case FunctionNode::FT_SWAP :
                {
                    //resta
                    //                    int result = 0;
                    //                    for (int i = 0; i < arity - 1; ++i) {
                    //                        result -= arguments[i]->getValue();
                    //                    }
                    //                    arguments[arity - 1]->setValue(result);
                    break;
                }
                default: DEBUGM(TO_STRING(function.getType()));
                    assert(0);
            }
        }
    };

    int GpModel::main( int argc, char** argv ) {
        GpModel model(30);
        model.setCrossoverOperator(new RegularAndFairSizeCrossover(200));
        model.setMutationOperator(new RegularMutation());
        model.setReproductionOperator(new RegularReproduction());
        model.getTreeGenerator().setPreferredDepth(2);
        model.getTreeGenerator().setPreferredMethod(TreeGenerator::M_RAMPED);
        model.setLowerFitnessBetter(true);

        model.create();

        model.setProbabilityCrossover(1.0);
        model.setProbabilityMutation(0.01);
        model.setProbabilityReproduction(1 - (model.getProbabilityCrossover() + model.getProbabilityMutation()));

        TestFunctionListener listener;
        //        for (int i = 0; i < 1000; ++i) {
        int i = 0;
        while (true) {
            ++i;
            double average_fitness = 0.0;
            double average_size = 0.0;
            for (int j = 0; j < model.getPopulationSize(); ++j) {
                TerminalNode* node = model.evaluate(model.getTree(j), listener);
                int fitness = node->getValue();
                delete node;
                model.getTree(j).setFitness(abs(fitness));
                average_fitness += abs(fitness);
                average_size += model.getTree(j).getSize();
            }
            BDEBUG("avg-size=" + TO_STRING(average_size / model.getPopulationSize()) + "\tavg-fitness=" + TO_STRING(average_fitness / model.getPopulationSize()) + "\tepoch=" + TO_STRING(i) + "");
            model.step();
            //            model.crossover();
        }
        return 0;
    }
}
